package ru.ifmo.mlarinsky.classificator.map;

import fi.javasom.jsom.InputVectors;
import fi.javasom.jsom.JSomLabeling;
import fi.javasom.jsom.JSomTraining;
import fi.javasom.jsom.SomNode;
import fi.javasom.jsom.WeightVectors;
import java.util.ArrayList;
import java.util.List;
import ru.ifmo.mlarinsky.classificator.providers.InstructionsProvider;

public class MapBuilder {

    public static SomMap buildMap(InstructionsProvider instructionsProvider) {
        InputVectors inputVectors = SomVectorsBuilder.buildInputVectors(
                instructionsProvider.getVectorProvider());
        WeightVectors weightVectors = SomVectorsBuilder.buildWeightVectors(instructionsProvider);

        JSomTraining traininer = new JSomTraining(weightVectors, inputVectors);
        traininer.setTrainingInstructions(instructionsProvider.getSteps(),
                instructionsProvider.getLearningRate(), instructionsProvider.getRadius(),
                instructionsProvider.getLearningRateType().toString(),
                instructionsProvider.getNeighbourhoodType().toString());

        traininer.doTraining();

        return buildSomMap(weightVectors, instructionsProvider);
    }

    public static SomMap labelMap(SomMap map,
            InstructionsProvider provider) {
        InputVectors inputVectors = SomVectorsBuilder.buildInputVectors(provider.getVectorProvider());
        WeightVectors weightVectors = SomVectorsBuilder.buildWeightVectors(provider);

        weightVectors.clear();
        weightVectors.addAll(map);

        JSomLabeling labeler = new JSomLabeling(weightVectors, inputVectors);

        return buildSomMap(labeler.doLabeling(), provider);
    }

    public static SomMap buildLabeledMap(InstructionsProvider instructionsProvider) {
        InputVectors inputVectors = SomVectorsBuilder.buildInputVectors(
                instructionsProvider.getVectorProvider());
        WeightVectors weightVectors = SomVectorsBuilder.buildWeightVectors(instructionsProvider);

        JSomTraining traininer = new JSomTraining(weightVectors, inputVectors);
        traininer.setTrainingInstructions(instructionsProvider.getSteps(),
                instructionsProvider.getLearningRate(), instructionsProvider.getRadius(),
                instructionsProvider.getLearningRateType().toString(),
                instructionsProvider.getNeighbourhoodType().toString());

        traininer.doTraining();

        JSomLabeling labeler = new JSomLabeling(weightVectors, inputVectors);

        return buildSomMap(labeler.doLabeling(), instructionsProvider);
    }

    private static SomMap buildSomMap(WeightVectors vectors,
            InstructionsProvider provider) {
//        for (Object object : vectors) {
//            SomNode node = (SomNode) object;
//            System.out.println(node.getLabel() + " -> " + Arrays.toString(node.getLocation()));
//        }
        return new SomMap(normalizeVectors(vectors, provider.getVectorProvider().getDimensions()),
                provider);
    }

    private static List<MapNode> normalizeVectors(WeightVectors vectors, String[] dimensions) {
        double epsilon = 0.0000001;
        int xDim = vectors.getXDimension();
        SomNode firstNonNullXNode = (SomNode) vectors.get(1);
        SomNode firstNonNullYNode = (SomNode) vectors.get(xDim + 1);
        double xStep = firstNonNullXNode.getLocation()[0];
        double yStep = firstNonNullYNode.getLocation()[1];

        List<MapNode> normalizedNodes = new ArrayList<MapNode>();
        for (Object object : vectors) {
            SomNode node = (SomNode) object;

            int x = (int) ((node.getLocation()[0] + epsilon) / xStep);
            int y = (int) ((node.getLocation()[1] + epsilon) / yStep);
            normalizedNodes.add(new MapNode(node, dimensions,  x, y));
        }

        return normalizedNodes;
    }

    public static void main(String[] args) {
//        testTrainMap();
//        testLabeledMap();
        testMapEquality();
    }

    private static void testTrainMap() {
        System.out.println(buildMap(InstructionsProvider.DEFAULTS));
    }

    private static void testLabeledMap() {
        SomMap map = buildMap(InstructionsProvider.DEFAULTS);
        System.out.println(labelMap(map, InstructionsProvider.DEFAULTS));
    }

    private static void testMapEquality() {
        SomMap map = buildMap(InstructionsProvider.DEFAULTS);
        SomMap labeledMap = labelMap(map, InstructionsProvider.DEFAULTS);

        if (map.size() != labeledMap.size()) {
            System.out.println("TEST FILAED. Reason: different sizes: map.size() = "
                    + map.size() + "\tlabeledMap.size() = " + labeledMap.size());
            return;
        }

        for (int i = 0; i < map.size(); i++) {
            double[] mapNodeValues = map.get(i).getValues();
            double[] labeledMapNodeValues = labeledMap.get(i).getValues();

            if (mapNodeValues.length != labeledMapNodeValues.length) {
                System.out.println("TEST FILAED. Reason: different nodes");
                return;
            }

            for (int j = 0; j < mapNodeValues.length; j++) {
                if (mapNodeValues[j] != labeledMapNodeValues[j]) {
                    System.out.println("TEST FILAED. Reason: different nodes");
                    return;
                }
            }
        }

        System.out.println("TEST PASSED.");
    }
}
